/*	Copyright 2007 - Xavier Baro (xbaro@cvc.uab.cat)

	This file is part of eapmlib.

    Eapmlib is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or any 
	later version.

    Eapmlib is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "IntSamplesSet.h"
#include "IntImageData.h"

Evolutive::CIntSamplesSet::CIntSamplesSet(void) : CSamplesSet(),m_HasIntInformation(false),m_HasRotatedIntInformation(false),m_IntSamplesSet(NULL),m_RIntSamplesSet(NULL),m_SqIntSamplesSet(NULL)
													
{
#ifndef USE_OPENCV
	OPENCV_ERROR_CLASS("CIntSamplesSet");
#else
	// By default select unsigned byte
	m_IntImgDepth=IPL_DEPTH_32S;
	m_SqIntImgDepth=IPL_DEPTH_64F;

	// Set a default size
	m_IntImgSize.width=m_ImgSize.width+1;
	m_IntImgSize.height=m_ImgSize.height+1;
#endif
}

Evolutive::CIntSamplesSet::~CIntSamplesSet(void)
{
	// Remove the data
	if(m_IntSamplesSet)
		delete[] m_IntSamplesSet;
	if(m_RIntSamplesSet)
		delete[] m_RIntSamplesSet;
	if(m_SqIntSamplesSet)
		delete[] m_SqIntSamplesSet;
}

#ifdef USE_OPENCV
void Evolutive::CIntSamplesSet::Serialize(std::fstream &fs,bool Saving)
{
	// Call the base class method
	CSamplesSet::Serialize(fs,Saving);

	// Implement the specialized code
	if(Saving)
	{
		// Save the data to a file
		// Save the flags
		fs.write((char*)&m_HasIntInformation,1);
		fs.write((char*)&m_HasRotatedIntInformation,1);		
		fs.write((char*)&m_IntImgSize,sizeof(m_IntImgSize));				
		fs.write((char*)&m_IntImgDepth,sizeof(int));
		fs.write((char*)&m_SqIntImgDepth,sizeof(int));
		fs.write((char*)&m_IntDataLen,sizeof(int));
		fs.write((char*)&m_SqIntDataLen,sizeof(int));

		// Save the images
		if(m_HasIntInformation)
		{
			fs.write((char*)m_IntSamplesSet,m_IntDataLen*m_NumImages);
			fs.write((char*)m_SqIntSamplesSet,m_SqIntDataLen*m_NumImages);
		}
		if(m_HasRotatedIntInformation)
		{
			fs.write((char*)m_RIntSamplesSet,m_IntDataLen*m_NumImages);
		}
	}
	else
	{
		// Load the data from a file
		// Load the flags
		fs.read((char*)&m_HasIntInformation,1);
		fs.read((char*)&m_HasRotatedIntInformation,1);
		fs.read((char*)&m_IntImgSize,sizeof(m_IntImgSize));		
		fs.read((char*)&m_IntImgDepth,sizeof(int));
		fs.read((char*)&m_SqIntImgDepth,sizeof(int));
		fs.read((char*)&m_IntDataLen,sizeof(int));
		fs.read((char*)&m_SqIntDataLen,sizeof(int));
			
		// Create the data storage for these images		
		if(m_IntSamplesSet)
			delete[] m_IntSamplesSet;
		if(m_RIntSamplesSet)
			delete[] m_RIntSamplesSet;
		if(m_SqIntSamplesSet)
			delete[] m_SqIntSamplesSet;
		m_IntSamplesSet=NULL;
		m_RIntSamplesSet=NULL;
		m_SqIntSamplesSet=NULL;	

		// Load the images		
		if(m_HasIntInformation)
		{
			m_IntSamplesSet=new unsigned char[m_NumImages*m_IntDataLen];
			m_SqIntSamplesSet=new unsigned char[m_NumImages*m_SqIntDataLen];
			fs.read((char*)m_IntSamplesSet,m_NumImages*m_IntDataLen);
			fs.read((char*)m_SqIntSamplesSet,m_NumImages*m_SqIntDataLen);
		}
		if(m_HasRotatedIntInformation)
		{
			m_RIntSamplesSet=new unsigned char[m_NumImages*m_IntDataLen];
			fs.read((char*)m_RIntSamplesSet,m_NumImages*m_IntDataLen);			
		}
	}	
}

void Evolutive::CIntSamplesSet::CalcIntImages(bool UseRotated)
{	
	register int i;
	IplImage *pRefImgHeader=NULL;	
	IplImage *ImgHeader=NULL,*IntHeader=NULL,*RIntHeader=NULL,*SqIntHeader=NULL;
	int ImgDesp,IntDesp,SqDesp;

	// Remove old memory
	if(m_IntSamplesSet)
		delete[] m_IntSamplesSet;
	if(m_SqIntSamplesSet)
		delete[] m_SqIntSamplesSet;
	if(m_RIntSamplesSet)
		delete[] m_RIntSamplesSet;	

	// Prepare object to calculate the integral images
	InitializeIntegralData();	

	// Create a header to obtain the real data lenght
	pRefImgHeader=cvCreateImageHeader(m_IntImgSize,m_IntImgDepth,m_NumChannels);
	m_IntDataLen=pRefImgHeader->imageSize;
	cvReleaseImageHeader(&pRefImgHeader);
	pRefImgHeader=cvCreateImageHeader(m_IntImgSize,m_SqIntImgDepth,m_NumChannels);
	m_SqIntDataLen=pRefImgHeader->imageSize;
	cvReleaseImageHeader(&pRefImgHeader);

	// Normal Integral images	
	m_IntSamplesSet=new unsigned char[m_NumImages*m_IntDataLen];
	m_SqIntSamplesSet=new unsigned char[m_NumImages*m_SqIntDataLen];

	// Check the memory allocation
	if(!m_IntSamplesSet || !m_SqIntSamplesSet)
		throw CEvolutiveLibException("Cannot create integral images matrix",__FILE__,__LINE__,"CalcIntImages");		

	// Activate the flag
	m_HasIntInformation=true;	

	// Rotated versions
	if(UseRotated)
	{	
		// Create the storing matrix
		m_RIntSamplesSet=new unsigned char[m_NumImages*m_IntDataLen];		
		
		// Check the memory allocation
		if(!m_RIntSamplesSet)
			throw CEvolutiveLibException("Cannot create rotated integral images matrix",__FILE__,__LINE__,"CalcIntImages");		

		// Activate the flag	
		m_HasRotatedIntInformation=true;
	}

	// Create the headers
	ImgHeader=cvCreateImageHeader(m_ImgSize,m_ImgDepth,m_NumChannels);
	IntHeader=cvCreateImageHeader(m_IntImgSize,m_IntImgDepth,m_NumChannels);
	RIntHeader=cvCreateImageHeader(m_IntImgSize,m_IntImgDepth,m_NumChannels);
	SqIntHeader=cvCreateImageHeader(m_IntImgSize,m_SqIntImgDepth,m_NumChannels);
	
	// Calculate an itegral image per channel
	if(m_NumChannels>1)
	{
		throw CEvolutiveLibException("TODO: Extend to color images",__FILE__,__LINE__,"CalcIntImages");
	}
	//for(c=0;c<m_NumChannels;c++)	
	{

		// Calculate the integral image for each image
		for(i=0;i<m_NumImages;i++)
		{
			// Calculate the position
			ImgDesp=i*m_DataLen;
			IntDesp=i*m_IntDataLen;
			SqDesp=i*m_SqIntDataLen;

			// Point to the data
			ImgHeader->imageData=(char*)&(m_SamplesSet[ImgDesp]);
			IntHeader->imageData=(char*)&(m_IntSamplesSet[IntDesp]);
			SqIntHeader->imageData=(char*)&(m_SqIntSamplesSet[SqDesp]);
			if(UseRotated)
				RIntHeader->imageData=(char*)&(m_RIntSamplesSet[IntDesp]);

			// Calculate the integral images
			if(UseRotated)
				cvIntegral(ImgHeader,IntHeader,SqIntHeader,RIntHeader);
			else
				cvIntegral(ImgHeader,IntHeader,SqIntHeader,NULL);
		}
	}

	// Release the headers
	cvReleaseImageHeader(&ImgHeader);
	cvReleaseImageHeader(&IntHeader);
	cvReleaseImageHeader(&RIntHeader);
	cvReleaseImageHeader(&SqIntHeader);
}

IplImage* Evolutive::CIntSamplesSet::GetIntImage(unsigned int Idx)
{
	IplImage *Img=NULL;

	// Verify the data 
	if(!m_IntSamplesSet)
		return NULL;

	// Create a header
	Img=cvCreateImageHeader(m_IntImgSize,m_IntImgDepth,m_NumChannels);

	// Point the header to the data
	Img->imageData=(char*)&(m_IntSamplesSet[m_IntDataLen*Idx]);
	return Img;
}

IplImage* Evolutive::CIntSamplesSet::GetRIntImage(unsigned int Idx)
{
	IplImage *Img=NULL;

	// Verify the data 
	if(!m_RIntSamplesSet)
		return NULL;

	// Create a header
	Img=cvCreateImageHeader(m_IntImgSize,m_IntImgDepth,m_NumChannels);

	// Point the header to the data
	Img->imageData=(char*)&(m_RIntSamplesSet[m_IntDataLen*Idx]);
	return Img;
}

Evolutive::CData* Evolutive::CIntSamplesSet::GetData(unsigned int Idx)
{
	IplImage *IntImg=NULL,*RIntImg=NULL;
	CIntImageData *pData=NULL;
	
	// Create a header
	IntImg=cvCreateImageHeader(m_IntImgSize,m_IntImgDepth,m_NumChannels);	

	// Point the header to the data
	IntImg->imageData=(char*)&(m_IntSamplesSet[m_IntDataLen*Idx]);

	if(m_HasRotatedIntInformation)
	{
		// Create a header
		RIntImg=cvCreateImageHeader(m_IntImgSize,m_IntImgDepth,m_NumChannels);

		// Point the header to the data
		RIntImg->imageData=(char*)&(m_RIntSamplesSet[m_IntDataLen*Idx]);
	}

	if(m_HasRotatedIntInformation)
	{		
		// Create the data structure
		pData=new CIntImageData(IntImg,RIntImg);
	}
	else
	{
		// Create the data structure
		pData=new CIntImageData(IntImg);
	}

	return (CData*)pData;
}

Evolutive::CData* Evolutive::CIntSamplesSet::operator[] (unsigned int Idx)
{
	return GetData(Idx);
}

void Evolutive::CIntSamplesSet::InitializeIntegralData(void)
{
	// Initialize the pointers
	m_IntSamplesSet=NULL;
	m_SqIntSamplesSet=NULL;
	m_RIntSamplesSet=NULL;

	// Disable the flags
	m_HasIntInformation=false;	
	m_HasRotatedIntInformation=false;	

	// Select the integral images depth
	switch(m_ImgDepth)
	{
		case IPL_DEPTH_8U:
		case IPL_DEPTH_8S:
			m_IntImgDepth=IPL_DEPTH_32S;		
			m_SqIntImgDepth=IPL_DEPTH_64F;				
			break;		
		case IPL_DEPTH_32F:
			m_IntImgDepth=IPL_DEPTH_64F;		
			m_SqIntImgDepth=IPL_DEPTH_64F;					
			break;		
		default:
			throw CEvolutiveLibException("Incorrect input image depth",__FILE__,__LINE__,"CalcIntImages");		
	}

	// Define the size of integral images
	m_IntImgSize.width=m_ImgSize.width+1;
	m_IntImgSize.height=m_ImgSize.height+1;
}

void Evolutive::CIntSamplesSet::CopyParameters(CSamplesSet *Object,int NumSamples)
{
	//! Call the parent function
	CSamplesSet::CopyParameters((CSamplesSet*)Object,NumSamples);

	//! Initialize the data
	((CIntSamplesSet*)Object)->InitializeIntegralData();
}

void Evolutive::CIntSamplesSet::SplitData(CIntSamplesSet *Train,CIntSamplesSet *Test,double TestPer)
{
	// Use the parent class definition
	CSamplesSet::SplitData((CSamplesSet*)Train,(CSamplesSet*)Test,TestPer);

	// Calculate the integral information
	Train->CalcIntImages(m_HasRotatedIntInformation);
	Test->CalcIntImages(m_HasRotatedIntInformation);
}

void Evolutive::CIntSamplesSet::MergeData(CIntSamplesSet *Samples1,CIntSamplesSet *Samples2)
{
	// Use the parent class definition
	CSamplesSet::MergeData((CSamplesSet*)Samples1,(CSamplesSet*)Samples2);

	// If some of the sets have rotated integral images, calculate it for the new set
	m_HasRotatedIntInformation=false;
	if(Samples1->m_HasRotatedIntInformation || Samples1->m_HasRotatedIntInformation)
		m_HasRotatedIntInformation=true;

	// Calculate the integral information
	CalcIntImages(m_HasRotatedIntInformation);
}

double Evolutive::CIntSamplesSet::GetNormFactor(unsigned int Idx)
{
/*    double Area;
	double ValSum;
	double ValSqSum;
	char *pData=NULL,*pSqData=NULL;
	int D0,D1,D2,D3;
	int WidthStep;
	
	// Point to the data
	pData=(char*)&(m_IntSamplesSet[m_IntDataLen*Idx]);
	pSqData=(char*)&(m_SqIntSamplesSet[m_SqIntDataLen*Idx]);

	// Calculate the widthstep
	WidthStep=m_DataLen/m_ImgSize.height;
	     
	// Calculate the image area    
    Area = m_ImgSize.width*m_ImgSize.height;

	// Calculate the sum of all the values on this region
	ValSum=pData[D0]+pData[D3]-(pData[D1]+pData[D2]);

	// Calculate the squared sum of all teh values in this region
	ValSqSum=pSqData[DataLen-
	
    // Calculate the normalization factor
    return sqrt( (double) (Area * ValSqSum - (double)ValSum * ValSum) );
	*/
	//TODO: Calculate the normalization factor
	return 1.0;
}

void Evolutive::CIntSamplesSet::UpdateSamples(CClassifier *Classifier)
{
	// Use the parent class definition
	CSamplesSet::UpdateSamples(Classifier);

	// Calculate the integral information
	CalcIntImages(m_HasRotatedIntInformation);
}
#endif//USE_OPENCV
